﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace Engine.Cameras
{
    public class EditorCamera:BaseCamera
    {
        public EditorCamera(Game game)
            : base(game)
        {
            CameraTarget = new Vector3(0, 0, 0);
            Zoom = 1000;
            Rotation = new Vector3(MathHelper.PiOver2, 0, 0);
        }
        #region Fields

        private MouseState? _previousMouseState;

        #endregion

        #region Properties

        private Vector3 _cameraPosition;
        public override Vector3 CameraPosition
        {
            get { return _cameraPosition; }
            set { _cameraPosition = value; }
        }
        public Vector3 CameraTarget { get; private set; }
        public Vector3 Rotation { get; private set; }
        public float Zoom { get; private set; }

        #endregion
        
        #region Override

        public override void Update(GameTime gameTime)
        {
            HandleInputs();
            UpdateRotationIfIsTooHigh();
            UpdateCameraPosition();

            ViewMatrix = Matrix.CreateLookAt(CameraPosition, CameraTarget, Vector3.Up);
            base.Update(gameTime);
        }

        #endregion

        #region Private methods

        private float? _previousWheelValue ;
        private void HandleInputs()
        {
            MouseState mouseState = Mouse.GetState();
            UpdateInputZoom(mouseState);
            UpdateInputScrollValues(mouseState);
            UpdateRotationInput(mouseState);
        }
        private void UpdateRotationInput(MouseState mouseState)
        {
            if (mouseState.LeftButton == ButtonState.Pressed)
            {
                if (_previousMouseState != null)
                {
                    int yDir = (mouseState.X - _previousMouseState.Value.X);
                    int xDir = -(mouseState.Y - _previousMouseState.Value.Y);

                    const float scaleFactor = 100;

                    if (System.Math.Abs(xDir) > System.Math.Abs(yDir))
                        Rotation = Vector3.Add(Rotation, new Vector3(xDir/scaleFactor, 0, 0));
                    else
                        Rotation = Vector3.Add(Rotation, new Vector3(0, yDir/scaleFactor, 0));

                    _previousMouseState = null;
                }
                else
                    _previousMouseState = mouseState;
            }
            else
                _previousMouseState = null;
        }
        private void UpdateInputScrollValues(MouseState mouseState)
        {
            if (mouseState.RightButton == ButtonState.Pressed)
            {
                const float speed = 10;
                const float margin = 50;

                if (mouseState.X > Game.GraphicsDevice.Viewport.Width - margin)
                {
                    ScrollRight(speed);
                }
                if (mouseState.Y > Game.GraphicsDevice.Viewport.Height - margin)
                {
                    ScrollForward(speed);
                }
                if (mouseState.X < margin)
                {
                    ScrollLeft(speed);
                }
                if (mouseState.Y < margin)
                {
                    ScrollBack(speed);
                }
            }
        }

        private void UpdateInputZoom(MouseState mouseState)
        {
            if (_previousWheelValue != null)
            {
                const int zoomScaleFactor = 20;
                Zoom += (_previousWheelValue.Value - mouseState.ScrollWheelValue)/zoomScaleFactor;
            }
            _previousWheelValue = mouseState.ScrollWheelValue;
        }

        private void UpdateRotationIfIsTooHigh()
        {
            if (Rotation.X >= MathHelper.PiOver2 - 0.1f)
                Rotation = new Vector3(MathHelper.PiOver2 - 0.1f, Rotation.Y, Rotation.Z);
            if (Rotation.X < 0)
                Rotation = new Vector3(0, Rotation.Y, Rotation.Z);
        }
        private void UpdateCameraPosition()
        {
            float r = Zoom;
            float x =CameraTarget.X + r * (float)System.Math.Cos(Rotation.Y) * (float)System.Math.Cos(Rotation.X);
            float y = CameraTarget.Y + r * (float)System.Math.Sin(Rotation.X);
            float z = CameraTarget.Z + r * (float)System.Math.Sin(Rotation.Y) * (float)System.Math.Cos(Rotation.X);
            _cameraPosition = new Vector3(x, y, z);
            UpdateCameraPosIfIsBelowTerrain();
       }

        private void UpdateCameraPosIfIsBelowTerrain()
        {
            if (World.Terrain.IsOnHeightmap(_cameraPosition))
            {
                float minimumHeight = World.Terrain.GetHeight(_cameraPosition);

                if (_cameraPosition.Y < minimumHeight)
                {
                    _cameraPosition = new Vector3(_cameraPosition.X, minimumHeight, _cameraPosition.Z);
                }
            }
        }
        private void ScrollLeft(float speed)
        {            
            float xOffset = speed * (float)System.Math.Cos(Rotation.Y);
            float zOffset = speed * (float)System.Math.Sin(Rotation.Y);
            CameraTarget += new Vector3(-zOffset, 0, xOffset);
        }
        private void ScrollBack(float speed)
        {
            float xOffset = speed * (float)System.Math.Cos(Rotation.Y);
            float zOffset = speed * (float)System.Math.Sin(Rotation.Y);
            CameraTarget += new Vector3(-xOffset, 0, -zOffset);

        }
        private void ScrollForward(float speed)
        {
            float xOffset = speed * (float)System.Math.Cos(Rotation.Y);
            float zOffset = speed * (float)System.Math.Sin(Rotation.Y);
            CameraTarget += new Vector3(xOffset, 0, zOffset);

        }
        private void ScrollRight(float speed)
        {
            float xOffset = (float)System.Math.Cos(Rotation.Y);
            float zOffset = (float)System.Math.Sin(Rotation.Y);
            CameraTarget += speed * new Vector3(zOffset, 0, -xOffset);

        }

        #endregion
    }
}
